package com.qf.elasticsearch.controller;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.qf.elasticsearch.pojo.Movie;
import com.qf.elasticsearch.pojo.News;
import org.elasticsearch.ElasticsearchException;
import org.elasticsearch.action.search.SearchRequestBuilder;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.Response;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.suggest.Suggest;
import org.elasticsearch.search.suggest.SuggestBuilder;
import org.elasticsearch.search.suggest.completion.CompletionSuggestion;
import org.elasticsearch.search.suggest.completion.CompletionSuggestionBuilder;
import org.elasticsearch.search.suggest.term.TermSuggestion;
import org.elasticsearch.search.suggest.term.TermSuggestionBuilder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.ResultsExtractor;
import org.springframework.data.elasticsearch.core.query.FetchSourceFilter;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

@RequestMapping("/search")
@RestController
@CrossOrigin(origins = "*")
public class SearchController {

    @Resource
    private ElasticsearchTemplate elasticsearchTemplate;

    // 推荐搜索, 使用前缀搜索
    @RequestMapping
    public Object suggest(String text) {
        // 中文的Completion Suggestion
        CompletionSuggestionBuilder cnCompletionSuggestion = new CompletionSuggestionBuilder("tags").prefix(text).skipDuplicates(true);
        // 基于拼音的Completion Suggestion
        CompletionSuggestionBuilder pyCompletionSuggestion = new CompletionSuggestionBuilder("tags.tag_pinyin").prefix(text).skipDuplicates(true);

        SuggestBuilder suggestBuilder = new SuggestBuilder()
                .addSuggestion("cn_suggestion", cnCompletionSuggestion)
                .addSuggestion("py_suggestion", pyCompletionSuggestion);

        // 构建搜索
        SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate
                .getClient() //
                .prepareSearch("news")
                .suggest(suggestBuilder)
                .setFetchSource(new String[]{"id"}, new String[]{});

        SearchResponse response = searchRequestBuilder.get();

        Set<String> suggestionWord = new HashSet<>();

        Suggest.Suggestion cnSuggestion = response.getSuggest().getSuggestion("cn_suggestion");
        generateSuggestion(cnSuggestion, suggestionWord);

        Suggest.Suggestion pySuggestion = response.getSuggest().getSuggestion("py_suggestion");
        generateSuggestion(pySuggestion, suggestionWord);

        return getResult(suggestionWord);
    }


    @RequestMapping("/execute")
    public Object executeSearch(@RequestParam(required = true) String text,
                                @RequestParam(required = false, defaultValue = "0") Integer page,
                                @RequestParam(required = false, defaultValue = "10") Integer size) {
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(new MultiMatchQueryBuilder(text, "title", "content"))
                .withSourceFilter(new FetchSourceFilter(new String[]{"id", "content", "title", "url"}, new String[]{}))
                .withIndices("news")
                .withHighlightBuilder(
                        new HighlightBuilder()
                                .field("title")
                                .field("content")
                                .postTags("</font>")
                                .preTags("<font color='red'>"))
                .withPageable(PageRequest.of(page, size))
                .build();

        return elasticsearchTemplate.query(searchQuery, new CustomResultExtractor());
    }


    public class CustomResultExtractor implements ResultsExtractor<List<News>> {

        @Override
        public List<News> extract(SearchResponse response) {
            return StreamSupport.stream(response.getHits().spliterator(), true)
                    .map(this::searchHitToMyClass)
                    .collect(Collectors.toList());
        }

        private News searchHitToMyClass(SearchHit searchHit) {
            News news = JSONObject.parseObject(searchHit.getSourceAsString(), News.class);

            Map<String, HighlightField> highlightFieldMap = searchHit.getHighlightFields();

            if(highlightFieldMap.containsKey("content")) {
                Text[] texts = searchHit.getHighlightFields().get("content").fragments();

                StringBuffer content = new StringBuffer();

                for(Text text : texts) {
                    if(null != text) {
                        content.append(text.toString());
                    }
                }
                news.setContent(content.toString());
            }

            if(highlightFieldMap.containsKey("title")) {
                Text[] texts = searchHit.getHighlightFields().get("title").fragments();

                StringBuffer title = new StringBuffer();

                for(Text text : texts) {
                    if(null != text) {
                        title.append(text.toString());
                    }
                }
                news.setTitle(title.toString());
            }

            return news;
        }
    }

    /**
     * @param suggestion  搜索出的建议

     */
    public void generateSuggestion(Suggest.Suggestion suggestion, Set<String> suggestionWord) {

        // suggestion.getEntries()方法得到的是对应的建议，因为在分词的情况下，可能有多个建议
        List<?> entries = suggestion.getEntries();

        if(entries.size() > 0) {
            for(Object obj : entries) {
                // 其实查出来的 obj 的类型为 CompletionSuggestion.Entry
                if(obj instanceof CompletionSuggestion.Entry) {
                    CompletionSuggestion.Entry entry = (CompletionSuggestion.Entry)obj;
                    // 获取具体的 option
                    List<CompletionSuggestion.Entry.Option> optionsList = entry.getOptions();
                    if(optionsList.size() > 0) {
                        for(CompletionSuggestion.Entry.Option op : optionsList) {
                            Text text = op.getText();
                            suggestionWord.add(text.toString());
                        }
                    }
                }
            }
        }
    }

    /**
     * @param set
     * @return   用于存储所有的建议，之所以使用 List<Map<String, String>> 这种数据类型，
     *           是因为 ElementUI 在autocomplete接收到的数据格式为  [{"value":"abc"}, {"value":"xyz"}]
     */
    public List<Map<String, String>> getResult(Set<String> set) {
        List<Map<String, String>> resultList = new LinkedList<>();
        set.forEach(suggestion -> {
            Map<String, String> map = new HashMap<>();
            map.put("value", suggestion);
            resultList.add(map);
        });
        return resultList;
    }

    /**
    @RequestMapping
    public Object search(String text) {

        TermSuggestionBuilder titleSuggest = new TermSuggestionBuilder("title").text(text)
                .suggestMode(TermSuggestionBuilder.SuggestMode.MISSING);

        TermSuggestionBuilder genreSuggest = new TermSuggestionBuilder("genre").text(text)
                .suggestMode(TermSuggestionBuilder.SuggestMode.MISSING);


        SuggestBuilder suggestBuilder = new SuggestBuilder();
        suggestBuilder.addSuggestion("title_suggest", titleSuggest).addSuggestion("genre_suggest", genreSuggest);

        SearchRequestBuilder searchRequestBuilder = elasticsearchTemplate.getClient()
                .prepareSearch("movies").suggest(suggestBuilder)
                .setQuery(
                        new BoolQueryBuilder().should(new MatchQueryBuilder("title", text))
                                              .should(new MatchQueryBuilder("genre", text))
                );


        SearchResponse response = searchRequestBuilder.get();  //发起请求, 返回响应

        SearchHit[] searchHitsArray = response.getHits().getHits(); //获取命中的数据

        Map<String, Object> results = new HashMap<>();

        if(searchHitsArray.length > 0) {
            results.put("code", 1); //有命中的数据
            List<Map<String, String>> resultsList = new ArrayList<>();

            for(SearchHit sh : searchHitsArray) {
                String resultJsonStr = sh.getSourceAsString();  //结果集的json数据

                Movie movie = JSONObject.parseObject(resultJsonStr, Movie.class, Feature.IgnoreNotMatch);

                Map<String, String> map = new HashMap<>();
                map.put("value", movie.getTitle());
                resultsList.add(map);
            }
            results.put("results", resultsList);

        }else {
            results.put("code", "-1");
            List<Map<String, String>> resultsList = new ArrayList<>();

            Suggest.Suggestion
                    titleSuggestion = response.getSuggest().getSuggestion("title_suggest");

            generateSuggestions(titleSuggestion, resultsList);

            Suggest.Suggestion genreSuggestion = response.getSuggest().getSuggestion("genre_suggest");

            generateSuggestions(genreSuggestion, resultsList);

            results.put("results", resultsList);
        }
        return results;
    }

    private void generateSuggestions(Suggest.Suggestion suggestion, List<Map<String, String>> list) {
        if(suggestion.getEntries().size() > 0)  {
            for(Object obj : suggestion.getEntries()) {
                if(obj instanceof TermSuggestion.Entry) {
                    TermSuggestion.Entry entry = (TermSuggestion.Entry)obj;
                    List<TermSuggestion.Entry.Option> options = entry.getOptions();
                    if(null != options && options.size() > 0) {
                        options.forEach(o -> {
                            //suggestStrs.add(o.getText().toString());
                            Map<String, String> map = new HashMap<>();
                            map.put("value", o.getText().toString());
                            list.add(map);
                        });
                    }
                }
            }
        }
    }
    */
}

